// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Windows;
using System.Windows.Input;
using Byond.UIRuntime;
using Utils;
using Utils.Documentation;

namespace Byond.PatchUiControls
{
	public abstract class RangeSliderBase : UiControl
	{
		protected RangeSliderBase()
		{
			Sensitivity = 0.3;
			Max = 1;
			Scale = ScaleBehavior.Lin;

			Loaded += OnLoaded;
		}

		#region IsSimpleSlider DP

		public static readonly DependencyProperty IsSimpleSliderProperty =
			DependencyProperty.Register(
				"IsSimpleSlider", typeof(bool), typeof(RangeSliderBase),
				new PropertyMetadata(default(bool)));

		public bool IsSimpleSlider
		{
			get { return (bool)GetValue(IsSimpleSliderProperty); }
			set { SetValue(IsSimpleSliderProperty, value); }
		}

		#endregion

		[Doc]
		public double Min { get; set; }

		[Doc]
		public double Max { get; set; }

		private double _valueTo;
		[Doc]
		public double ValueTo
		{
			get { return _valueTo; }
			set { OnValuePropertiesChanged(_valueFrom, value); }
		}

		private double _valueFrom;
		[Doc]
		public double ValueFrom
		{
			get { return _valueFrom; }
			set { OnValuePropertiesChanged(value, _valueTo); }
		}

		[Doc]
		public double Value
		{
			get { return ValueTo; }
			set { ValueTo = value; }
		}

		[Doc]
		public ScaleBehavior Scale { get; set; }

		[Doc]
		public double StepSize { get; set; }

		[Doc]
		public double Sensitivity { get; set; }

		public override bool IsSmoothEnabled { get { return StepSize == 0d; } }

		private void OnValuePropertiesChanged(double valueFrom, double valueTo)
		{
			SetInnerValue(valueFrom, valueTo);
		}

		private void SetInnerValue(double valueFrom, double valueTo)
		{
			_valueFrom = StepSize <= 0 ? valueFrom : Math.Round(valueFrom / StepSize) * StepSize;
			_valueTo = StepSize <= 0 ? valueTo : Math.Round(valueTo / StepSize) * StepSize;

			_valueFrom = Math.Max(Min, _valueFrom);
			_valueFrom = Math.Min(Max, _valueFrom);
			_valueTo = Math.Min(Max, _valueTo);
			_valueTo = Math.Max(Min, _valueTo);

			if (_sliderMode == SliderMode.ChangeUpperBound)
				_valueTo = Math.Max(_valueFrom, _valueTo);
			else
				_valueFrom = Math.Min(_valueFrom, _valueTo);

			OnValuesChanged();

			OnPropertyChanged("ValueFrom");
			OnPropertyChanged("ValueTo");
			OnPropertyChanged("Value");
		}

		protected abstract void OnValuesChanged();

		protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
		{
			base.OnRenderSizeChanged(sizeInfo);
			OnValuesChanged();
		}

		private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
		{
			Loaded -= OnLoaded;

			// Anfangswert-Validierung machen
			SetInnerValue(_valueFrom, _valueTo);
		}

		#region Capture / Move

		private SliderMode _sliderMode;
		private double _startMouseY;
		private double _lastMouseY;
		private double _startValueFrom;
		private double _startValueTo;

		protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
		{
			EnableBindToMe = false;

			_sliderMode = SliderMode.ChangeUpperBound;
			_startMouseY = e.GetPosition(this).Y;
			_lastMouseY = _startMouseY;
			_startValueFrom = ValueFrom;
			_startValueTo = ValueTo;

			CaptureMouse();

			e.Handled = true;
		}

		protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
		{
			EnableBindToMe = true;
			ReleaseMouseCapture();
			_sliderMode = SliderMode.Off;

			e.Handled = true;
		}

		protected override void OnPreviewMouseRightButtonDown(MouseButtonEventArgs e)
		{
			if (IsSimpleSlider)
				return;

			EnableBindToMe = false;

			_sliderMode = SliderMode.ChangeLowerBound;
			_startMouseY = e.GetPosition(this).Y;
			_lastMouseY = _startMouseY;
			_startValueFrom = ValueFrom;
			_startValueTo = ValueTo;

			CaptureMouse();

			e.Handled = true;
		}

		protected override void OnPreviewMouseRightButtonUp(MouseButtonEventArgs e)
		{
			if (IsSimpleSlider)
				return;

			EnableBindToMe = true;
			ReleaseMouseCapture();
			_sliderMode = SliderMode.Off;
			e.Handled = true;
		}

		protected override void OnPreviewMouseMove(MouseEventArgs e)
		{
			if (_sliderMode == SliderMode.Off)
			{
				base.OnPreviewMouseMove(e);
				return;
			}

			e.Handled = true;
			WasChangedByUser = true;

			// TODO: Evtl mit Strategy machen

			if (Scale == ScaleBehavior.Lin)
			{
				var currentMouseY = e.GetPosition(this).Y;
				var diffY = _startMouseY - currentMouseY;

				// 1 Mouse-Unit means: y% (Sensitivity) of the scale
				Func<double, double> calc = value =>
					value + diffY * (Sensitivity / 100) * (Max - Min);

				if (_sliderMode == SliderMode.ChangeLowerBound)
					SetInnerValue(calc(_startValueFrom), ValueTo);
				else
					SetInnerValue(ValueFrom, calc(_startValueTo));
			}
			else if (Scale != ScaleBehavior.Lin)
			{
				var powBase = 1d;
				var percFactor = 1d;

				if (Scale == ScaleBehavior.Log1)
				{
					powBase = 1.5;
					percFactor = 10;
				}
				else if (Scale == ScaleBehavior.Log2)
				{
					powBase = 2;
					percFactor = 5;
				}
				else if (Scale == ScaleBehavior.Log3)
				{
					powBase = 3;
					percFactor = 2;
				}

				var currentMouseY = e.GetPosition(this).Y;
				var delta = (_lastMouseY - currentMouseY) * Sensitivity * 5;
				_lastMouseY = currentMouseY;

				Func<double, double> calc = value =>
					{
						var perc = MathHelper.Map(Min, 0, Max, 1, value);
						var factor = Math.Pow(powBase, perc * percFactor);
						return value + delta * factor;
					};

				if (_sliderMode == SliderMode.ChangeLowerBound)
					SetInnerValue(calc(_valueFrom), ValueTo);
				else
					SetInnerValue(ValueFrom, calc(_valueTo));
			}
			else
				throw new Exception("Scale behavior not known: " + Scale);
		}

		#endregion
	}
}